/*
 * Copyright (C) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package com.github.florent37.camerafragment.internal.utils;

import com.github.florent37.camerafragment.configuration.Configuration;

import ohos.app.Context;
import ohos.media.recorder.RecorderProfile;

import java.io.File;
import java.io.Serializable;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.UUID;

/**
 * Camera帮助类
 *
 * @since 2021-05-25
 */
public final class CameraHelper {
    private static final String TAG = "CameraHelper";
    private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy_MM_dd_HH_mm_ss");
    private static final double CONSTANT_0_1 = 0.1;
    private static final int CONSTANT_2 = 2;
    private static final int CONSTANT_4 = 4;
    private static final int CONSTANT_8 = 8;
    private static final int CONSTANT_100 = 100;

    private CameraHelper() {
    }

    /**
     * 获取媒体输出文件
     *
     * @param context 上下文
     * @param mediaAction 媒体操作类型
     * @param pathToDirectory 文件路径
     * @param fileName 文件名
     * @return File文件
     */
    public static File getOutputMediaFile(Context context, int mediaAction, String pathToDirectory, String fileName) {
        final File mediaStorageDir = generateStorageDir(context, pathToDirectory);
        File mediaFile = null;
        String fileName1 = null;
        if (mediaStorageDir != null) {
            if (fileName == null) {
                if (mediaAction == Configuration.MEDIA_ACTION_PHOTO) {
                    fileName1 = "IMG_";
                } else if (mediaAction == Configuration.MEDIA_ACTION_VIDEO) {
                    fileName1 = "VID_";
                }
            }
            final String mediaStorageDirPath = mediaStorageDir.getPath();
            if (mediaAction == Configuration.MEDIA_ACTION_PHOTO) {
                mediaFile = new File(mediaStorageDirPath + File.separator + fileName1 + UUID.randomUUID() + ".jpg");
            } else if (mediaAction == Configuration.MEDIA_ACTION_VIDEO) {
                mediaFile = new File(mediaStorageDirPath + File.separator + fileName1 + UUID.randomUUID() + ".mp4");
            }
        }

        return mediaFile;
    }

    /**
     * 获取外部存储Dir
     *
     * @param context 上下文
     * @param pathToDirectory 存储路径
     * @return File文件
     */
    public static File generateStorageDir(Context context, String pathToDirectory) {
        File mediaStorageDir = null;
        if (pathToDirectory != null) {
            mediaStorageDir = new File(pathToDirectory);
        } else {
            mediaStorageDir = new File(context.getExternalFilesDir("").getAbsolutePath());
        }

        if (!mediaStorageDir.exists()) {
            if (!mediaStorageDir.mkdirs()) {
                Log.debug(TAG, "Failed to create directory.");
                return new File(pathToDirectory);
            }
        }

        return mediaStorageDir;
    }

    /**
     * 摄像机配置文件
     *
     * @param currentCameraId 当前相机Id
     * @param maxFileSize 最大文件大小
     * @param durationSeconds 最小持续时间(秒)
     * @return RecorderProfile录像配置文件
     */
    public static RecorderProfile getCamcorderProfile(int currentCameraId, long maxFileSize, int durationSeconds) {
        if (maxFileSize <= 0) {
            return RecorderProfile.getParameter(String.valueOf(currentCameraId), Configuration.MEDIA_QUALITY_HIGHEST);
        }

        int[] qualities = new int[]{Configuration.MEDIA_QUALITY_HIGHEST,
            Configuration.MEDIA_QUALITY_HIGH, Configuration.MEDIA_QUALITY_MEDIUM,
            Configuration.MEDIA_QUALITY_LOW, Configuration.MEDIA_QUALITY_LOWEST};

        RecorderProfile camcorderProfile;
        for (int ii = 0; ii < qualities.length; ++ii) {
            camcorderProfile = CameraHelper.getCamcorderProfile(qualities[ii], currentCameraId);
            double fileSize = CameraHelper.calculateApproximateVideoSize(camcorderProfile, durationSeconds);

            if (fileSize > maxFileSize) {
                long minimumRequiredBitRate = calculateMinimumRequiredBitRate(
                    camcorderProfile, maxFileSize, durationSeconds);

                if (minimumRequiredBitRate >= camcorderProfile.vBitRate / CONSTANT_4
                    && minimumRequiredBitRate <= camcorderProfile.vBitRate) {
                    camcorderProfile.vBitRate = (int) minimumRequiredBitRate;
                    return camcorderProfile;
                }
            } else {
                return camcorderProfile;
            }
        }
        return CameraHelper.getCamcorderProfile(Configuration.MEDIA_QUALITY_LOWEST, currentCameraId);
    }

    /**
     * 获取摄像机配置
     *
     * @param cameraId 相机ID
     * @param maxFileSize 最大文件大小
     * @param durationSeconds 最小持续时间(秒)
     * @return RecorderProfile录像配置文件
     */
    public static RecorderProfile getCamcorderProfile(String cameraId, long maxFileSize, int durationSeconds) {
        if ("".equals(cameraId)) {
            return getCamcorderProfile(0, maxFileSize, durationSeconds);
        }
        int cameraIdInt = Integer.parseInt(cameraId);
        return getCamcorderProfile(cameraIdInt, maxFileSize, durationSeconds);
    }

    /**
     * 获取摄像机配置
     *
     * @param mediaQuality 摄像机质量类型
     * @param cameraId 相机Id
     * @return RecorderProfile录像配置文件
     */
    public static RecorderProfile getCamcorderProfile(@Configuration.MediaQuality int mediaQuality, int cameraId) {
        if (mediaQuality == Configuration.MEDIA_QUALITY_HIGHEST) {
            return RecorderProfile.getParameter(String.valueOf(cameraId), RecorderProfile.QUALITY_LEVEL_HIGH);
        } else if (mediaQuality == Configuration.MEDIA_QUALITY_HIGH) {
            if (RecorderProfile.isProfile(cameraId, RecorderProfile.QUALITY_LEVEL_1080P)) {
                return RecorderProfile.getParameter(String.valueOf(cameraId), RecorderProfile.QUALITY_LEVEL_1080P);
            } else if (RecorderProfile.isProfile(cameraId, RecorderProfile.QUALITY_LEVEL_720P)) {
                return RecorderProfile.getParameter(String.valueOf(cameraId), RecorderProfile.QUALITY_LEVEL_720P);
            } else {
                return RecorderProfile.getParameter(String.valueOf(cameraId), RecorderProfile.QUALITY_LEVEL_HIGH);
            }
        } else if (mediaQuality == Configuration.MEDIA_QUALITY_MEDIUM) {
            if (RecorderProfile.isProfile(cameraId, RecorderProfile.QUALITY_LEVEL_720P)) {
                return RecorderProfile.getParameter(String.valueOf(cameraId), RecorderProfile.QUALITY_LEVEL_720P);
            } else if (RecorderProfile.isProfile(cameraId, RecorderProfile.QUALITY_LEVEL_480P)) {
                return RecorderProfile.getParameter(String.valueOf(cameraId), RecorderProfile.QUALITY_LEVEL_480P);
            } else {
                return RecorderProfile.getParameter(String.valueOf(cameraId), RecorderProfile.QUALITY_LEVEL_LOW);
            }
        } else if (mediaQuality == Configuration.MEDIA_QUALITY_LOW) {
            if (RecorderProfile.isProfile(cameraId, RecorderProfile.QUALITY_LEVEL_480P)) {
                return RecorderProfile.getParameter(String.valueOf(cameraId), RecorderProfile.QUALITY_LEVEL_480P);
            } else {
                return RecorderProfile.getParameter(String.valueOf(cameraId), RecorderProfile.QUALITY_LEVEL_LOW);
            }
        } else if (mediaQuality == Configuration.MEDIA_QUALITY_LOWEST) {
            return RecorderProfile.getParameter(String.valueOf(cameraId), RecorderProfile.QUALITY_LEVEL_LOW);
        } else {
            return RecorderProfile.getParameter(String.valueOf(cameraId), RecorderProfile.QUALITY_LEVEL_HIGH);
        }
    }

    /**
     * 获取摄像机配置
     *
     * @param mediaQuality 媒体质量类型
     * @param cameraId 相机Id
     * @return RecorderProfile录像配置文件
     */
    public static RecorderProfile getCamcorderProfile(@Configuration.MediaQuality int mediaQuality, String cameraId) {
        if ("".equals(cameraId)) {
            return getCamcorderProfile(mediaQuality, 0);
        }
        int cameraIdInt = Integer.parseInt(cameraId);
        return getCamcorderProfile(mediaQuality, cameraIdInt);
    }

    private static double calculateApproximateVideoSize(RecorderProfile camcorderProfile, int seconds) {
        return ((camcorderProfile.vBitRate / (float) 1
            + camcorderProfile.aBitRate / (float) 1) * seconds) / (float) CONSTANT_8;
    }

    private static long calculateMinimumRequiredBitRate(RecorderProfile camcorderProfile,
                                                        long maxFileSize, int seconds) {
        return CONSTANT_8 * maxFileSize / seconds - camcorderProfile.aBitRate;
    }

    /**
     * 获得大小与最接近的比率
     *
     * @param sizes 大小集合
     * @param width 宽度
     * @param height 高度
     * @return Size大小
     */
    public static Size getSizeWithClosestRatio(List<Size> sizes, int width, int height) {
        if (sizes == null) {
            return new Size();
        }

        double minTolerance = CONSTANT_100;
        double targetRatio = (double) height / width;
        Size optimalSize = null;
        double minDiff = Double.MAX_VALUE;

        int targetHeight = height;

        for (Size size : sizes) {
            if (size.getWidth() == width && size.getHeight() == height) {
                return size;
            }

            double ratio = (double) size.getHeight() / size.getWidth();

            if (Math.abs(ratio - targetRatio) < minTolerance) {
                minTolerance = Math.abs(ratio - targetRatio);
                minDiff = Double.MAX_VALUE;
            } else {
                continue;
            }

            if (Math.abs(size.getHeight() - targetHeight) < minDiff) {
                optimalSize = size;
                minDiff = Math.abs(size.getHeight() - targetHeight);
            }
        }

        if (optimalSize == null) {
            minDiff = Double.MAX_VALUE;
            for (Size size : sizes) {
                if (Math.abs(size.getHeight() - targetHeight) < minDiff) {
                    optimalSize = size;
                    minDiff = Math.abs(size.getHeight() - targetHeight);
                }
            }
        }
        return optimalSize;
    }

    /**
     * 获得大小与最接近的比率
     *
     * @param sizes 大小数组
     * @param width 宽度
     * @param height 高度
     * @return Size大小
     */
    public static Size getSizeWithClosestRatio(Size[] sizes, int width, int height) {
        if (sizes == null) {
            return new Size();
        }

        double minTolerance = CONSTANT_100;
        double targetRatio = (double) height / width;
        Size optimalSize = null;
        double minDiff = Double.MAX_VALUE;

        int targetHeight = height;

        for (Size size : sizes) {
            double ratio = (double) size.getHeight() / size.getWidth();

            if (Math.abs(ratio - targetRatio) < minTolerance) {
                minTolerance = Math.abs(ratio - targetRatio);
                minDiff = Double.MAX_VALUE;
            } else {
                continue;
            }

            if (Math.abs(size.getHeight() - targetHeight) < minDiff) {
                optimalSize = size;
                minDiff = Math.abs(size.getHeight() - targetHeight);
            }
        }

        if (optimalSize == null) {
            minDiff = Double.MAX_VALUE;
            for (Size size : sizes) {
                if (Math.abs(size.getHeight() - targetHeight) < minDiff) {
                    optimalSize = size;
                    minDiff = Math.abs(size.getHeight() - targetHeight);
                }
            }
        }
        return optimalSize;
    }

    /**
     * 获取图片大小
     *
     * @param sizes 大小数组
     * @param mediaQuality 图片画质
     * @return Size大小
     */
    public static Size getPictureSize(Size[] sizes, @Configuration.MediaQuality int mediaQuality) {
        if (sizes == null || sizes.length == 0) {
            return new Size();
        }

        List<Size> choices = Arrays.asList(sizes);

        if (choices.size() == 1) {
            return choices.get(0);
        }

        Size result = null;
        Size maxPictureSize = Collections.max(choices, new CompareSizesByArea2());
        Size minPictureSize = Collections.min(choices, new CompareSizesByArea2());
        Collections.sort(choices, new CompareSizesByArea2());
        switch (mediaQuality) {
            case Configuration.MEDIA_QUALITY_HIGHEST:
                result = maxPictureSize;
                break;
            case Configuration.MEDIA_QUALITY_LOW:
                if (choices.size() == CONSTANT_2) {
                    result = minPictureSize;
                } else {
                    int half = choices.size() / CONSTANT_2;
                    int lowQualityIndex = (choices.size() - half) / CONSTANT_2;
                    result = choices.get(lowQualityIndex + 1);
                }
                break;
            case Configuration.MEDIA_QUALITY_HIGH:
                if (choices.size() == CONSTANT_2) {
                    result = maxPictureSize;
                } else {
                    int half = choices.size() / CONSTANT_2;
                    int highQualityIndex = (choices.size() - half) / CONSTANT_2;
                    result = choices.get(choices.size() - highQualityIndex - 1);
                }
                break;
            case Configuration.MEDIA_QUALITY_MEDIUM:
                if (choices.size() == CONSTANT_2) {
                    result = minPictureSize;
                } else {
                    int mediumQualityIndex = choices.size() / CONSTANT_2;
                    result = choices.get(mediumQualityIndex);
                }
                break;
            case Configuration.MEDIA_QUALITY_LOWEST:
                result = minPictureSize;
                break;
            default:
                break;
        }
        return result;
    }

    /**
     * 获取图片大小
     *
     * @param choices 大小集合
     * @param mediaQuality 图片质量类型
     * @return Size大小
     */
    public static Size getPictureSize(List<Size> choices, @Configuration.MediaQuality int mediaQuality) {
        if (choices == null || choices.isEmpty()) {
            return new Size();
        }
        if (choices.size() == 1) {
            return choices.get(0);
        }

        Size result = null;
        Size maxPictureSize = Collections.max(choices, new CompareSizesByArea2());
        Size minPictureSize = Collections.min(choices, new CompareSizesByArea2());

        Collections.sort(choices, new CompareSizesByArea2());
        switch (mediaQuality) {
            case Configuration.MEDIA_QUALITY_HIGHEST:
                result = maxPictureSize;
                break;
            case Configuration.MEDIA_QUALITY_LOW:
                if (choices.size() == CONSTANT_2) {
                    result = minPictureSize;
                } else {
                    int half = choices.size() / CONSTANT_2;
                    int lowQualityIndex = (choices.size() - half) / CONSTANT_2;
                    result = choices.get(lowQualityIndex + 1);
                }
                break;
            case Configuration.MEDIA_QUALITY_HIGH:
                if (choices.size() == CONSTANT_2) {
                    result = maxPictureSize;
                } else {
                    int half = choices.size() / CONSTANT_2;
                    int highQualityIndex = (choices.size() - half) / CONSTANT_2;
                    result = choices.get(choices.size() - highQualityIndex - 1);
                }
                break;
            case Configuration.MEDIA_QUALITY_MEDIUM:
                if (choices.size() == CONSTANT_2) {
                    result = minPictureSize;
                } else {
                    int mediumQualityIndex = choices.size() / CONSTANT_2;
                    result = choices.get(mediumQualityIndex);
                }
                break;
            case Configuration.MEDIA_QUALITY_LOWEST:
                result = minPictureSize;
                break;
            default:
                break;
        }
        return result;
    }

    /**
     * 获取图片分辨率
     *
     * @param choices 大小集合
     * @param mediaQuality 图片质量类型
     * @return Size大小
     */
    public static Size getPictureSize2(List<Size> choices, @Configuration.MediaQuality int mediaQuality) {
        if (choices == null || choices.isEmpty()) {
            return new Size();
        }
        if (choices.size() == 1) {
            return choices.get(0);
        }
        Size result = null;
        Size maxPictureSize = Collections.max(choices, new CompareSizesByArea2());
        Size minPictureSize = Collections.min(choices, new CompareSizesByArea2());
        switch (mediaQuality) {
            case Configuration.MEDIA_QUALITY_HIGHEST:
                result = maxPictureSize;
                break;
            case Configuration.MEDIA_QUALITY_LOW:
                if (choices.size() == CONSTANT_2) {
                    result = minPictureSize;
                } else {
                    int half = choices.size() / CONSTANT_2;
                    int lowQualityIndex = (choices.size() - half) / CONSTANT_2;
                    result = choices.get(lowQualityIndex + 1);
                }
                break;
            case Configuration.MEDIA_QUALITY_HIGH:
                int half = choices.size() / CONSTANT_2;
                int lowQualityIndex = (choices.size() - half) / CONSTANT_2;
                result = choices.get(lowQualityIndex);
                break;
            case Configuration.MEDIA_QUALITY_MEDIUM:
                int half1 = choices.size() / CONSTANT_2;
                result = choices.get(half1);
                break;
            case Configuration.MEDIA_QUALITY_LOWEST:
                result = minPictureSize;
                break;
            default:
                break;
        }
        return result;
    }

    /**
     * 比较大小
     *
     * @since 2021-05-25
     */
    private static class CompareSizesByArea2 implements Serializable, Comparator<Size> {
        @Override
        public int compare(Size lhs, Size rhs) {
            // We cast here to ensure the multiplications won't overflow
            return Long.signum((long) lhs.getWidth() * lhs.getHeight()
                - (long) rhs.getWidth() * rhs.getHeight());
        }
    }

    /**
     * 计算近似视频持续时间
     *
     * @param camcorderProfile 录像配置文件
     * @param maxFileSize 最大文件大小
     * @return double类型
     */
    public static double calculateApproximateVideoDuration(RecorderProfile camcorderProfile, long maxFileSize) {
        double profile = camcorderProfile.vBitRate + camcorderProfile.aBitRate;
        return CONSTANT_8 * (double) maxFileSize / profile;
    }

    /**
     * 选择最佳大小
     *
     * @param choices 大小数组
     * @param width 宽度
     * @param height 高度
     * @param aspectRatio 宽高比大小
     * @return Size大小
     */
    public static Size chooseOptimalSize(Size[] choices, int width, int height, Size aspectRatio) {
        List<Size> bigEnough = new ArrayList<>();
        int wid = aspectRatio.getWidth();
        int hei = aspectRatio.getHeight();
        for (Size option : choices) {
            if (option.getHeight() == option.getWidth() * hei / wid
                && option.getWidth() >= width && option.getHeight() >= height) {
                bigEnough.add(option);
            }
        }

        // Pick the smallest of those, assuming we found any
        if (bigEnough.size() > 0) {
            return Collections.min(bigEnough, new CompareSizesByArea2());
        } else {
            Log.error(TAG, "Couldn't find any suitable preview size");
            return new Size();
        }
    }

    /**
     * 获得最佳预览大小
     *
     * @param sizes 大小数组
     * @param width 宽度
     * @param height 高度
     * @return Size大小
     */
    public static Size getOptimalPreviewSize(Size[] sizes, int width, int height) {
        if (sizes == null) {
            return new Size();
        }

        double aspectTolerance = CONSTANT_0_1;
        double targetRatio = (double) height / width;
        Size optimalSize = null;
        double minDiff = Double.MAX_VALUE;

        int targetHeight = height;

        for (Size size : sizes) {
            double ratio = (double) size.getWidth() / size.getHeight();
            if (Math.abs(ratio - targetRatio) > aspectTolerance) {
                continue;
            }
            if (Math.abs(size.getHeight() - targetHeight) < minDiff) {
                optimalSize = size;
                minDiff = Math.abs(size.getHeight() - targetHeight);
            }
        }

        if (optimalSize == null) {
            minDiff = Double.MAX_VALUE;
            for (Size size : sizes) {
                if (Math.abs(size.getHeight() - targetHeight) < minDiff) {
                    optimalSize = size;
                    minDiff = Math.abs(size.getHeight() - targetHeight);
                }
            }
        }
        return optimalSize;
    }
}
